﻿using RockBus.Logging.Interfaces;
using RockBus.Messages.Interfaces;
using RockBus.PerformanceCounters.Interfaces;
using RockBus.Routing.Interfaces;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.ServiceModel.Channels;
using System.Threading;
using System.Xml;

namespace RockBus.Transport.File
{
    /// <summary>
    /// FileMessagePublisher is only intended for demo purposes. If you need proper
    ///  durable messaging, use a queueing framework or database.
    /// </summary>
    public class FileMessagePublisher : IMessagePublisher
    {
        private const string OperationCounterName = "PublishMessage";

        public FileMessagePublisher(
            ILogger logger,
            IPerformanceCounterFactory performanceCounterFactory,
            IMessageFactory messageFactory,
            IQueueManager queueManager)
        {
            this.Logger = logger;
            this.Logger = logger;
            this.PerformanceCounterFactory = performanceCounterFactory;
            this.MessageFactory = messageFactory;
            this.QueueManager = queueManager;

            this.ReaderSettings = new XmlReaderSettings();
            this.ReaderSettings.ConformanceLevel = ConformanceLevel.Auto;
            this.ReaderSettings.CloseInput = false;

            this.Configurations = FileMessagePublisherConfigurationSection.Instance.FileMessagePublisherConfigurations;

            this.PollTimers = new Dictionary<string, Timer>();
        }

        private ILogger Logger { get; set; }

        private IPerformanceCounterFactory PerformanceCounterFactory { get; set; }

        private IMessageFactory MessageFactory { get; set; }

        private IQueueManager QueueManager { get; set; }

        public XmlReaderSettings ReaderSettings { get; private set; }

        private FileMessagePublisherConfigurations Configurations { get; set; }

        private readonly object pollTimersLockObject = new object();

        private Dictionary<string, Timer> PollTimers { get; set; }

        public void Enable()
        {
            foreach (FileMessagePublisherConfiguration configuration in this.Configurations)
            {
                try
                {
                    this.EnableConfiguration(configuration);
                }
                catch (Exception exc)
                {
                    this.Logger.Log(MethodBase.GetCurrentMethod(), exc);
                }
            }
        }

        public void EnableConfiguration(FileMessagePublisherConfiguration configuration)
        {
            if (!this.PollTimers.ContainsKey(configuration.Id))
            {
                lock (pollTimersLockObject)
                {
                    if (!this.PollTimers.ContainsKey(configuration.Id))
                    {
                        this.PollTimers.Add(configuration.Id,
                            new Timer(
                                new TimerCallback(this.PollTimerCallback),
                                configuration,
                                configuration.PollingIntervalMs,
                                configuration.PollingIntervalMs));
                    }
                }
            }
        }

        public void Disable()
        {
            foreach (FileMessagePublisherConfiguration configuration in this.Configurations)
            {
                try
                {
                    this.DisableConfiguration(configuration);
                }
                catch (Exception exc)
                {
                    this.Logger.Log(MethodBase.GetCurrentMethod(), exc);
                }
            }
        }

        public void DisableConfiguration(FileMessagePublisherConfiguration configuration)
        {
            if (this.PollTimers.ContainsKey(configuration.Id))
            {
                lock (pollTimersLockObject)
                {
                    if (this.PollTimers.ContainsKey(configuration.Id))
                    {
                        Timer t = this.PollTimers[configuration.Id];
                        this.PollTimers.Remove(configuration.Id);
                        t.Dispose();
                    }
                }
            }
        }

        private int _isRunning;

        private void PollTimerCallback(object state)
        {
            if (null == state)
            {
                return;
            }

            FileMessagePublisherConfiguration configuration = state as FileMessagePublisherConfiguration;
            if (null == configuration)
            {
                return;
            }

            if (Interlocked.CompareExchange(ref _isRunning, 1, 0) == 1)
            {
                return;
            }

            try
            {
                DirectoryInfo di = new DirectoryInfo(configuration.Path);
                FileInfo[] fis = di.GetFiles(configuration.Filter);
                if ((null == fis) || (fis.Length == 0))
                {
                    return;
                }

                foreach (var fi in fis)
                {
                    this.ProcessMessage(fi);
                }
            }
            catch
            {
            }
            finally
            {
                _isRunning = 0;
            }
        }

        private void ProcessMessage(FileInfo fi)
        {
            this.Logger.LogDebug(MethodBase.GetCurrentMethod(), "Processing file {0}", fi.FullName);

            if (fi.Length > int.MaxValue)
            {
                throw new InvalidDataException("Cannot handle files larger than 2GB.");
            }

            var reader = XmlReader.Create(fi.FullName, this.ReaderSettings);
            var mc = this.MessageFactory.CreateMessageContext(reader, MessageVersion.Default);

            this.QueueManager.ProcessMessage(mc);
            fi.Delete(); // Oops if there is an error during processing...
        }
    }
}